AWS CDK(TypeScript)で Python ランタイムの Lambda 関数を実装してみた
こんにちは、CX 事業本部 Delivery 部の若槻です。
今回は、AWS CDK(TypeScript)で Python ランタイムの Lambda 関数を実装する方法を確認してみます。
使用したのは Amazon Lambda Python Library です。現在は Alpha 版となります。
このライブラリの PythonFunction
を使うことにより、NodejsFunctionと同様に、モジュールのパッケージングをよしなにやってくれます。
試してみた
AWS CDK(TypeScript)で Python ランタイムの Lambda 関数を実装してみます。
プロジェクト作成
CDK プロジェクトを初回作成します。言語は TypeScript です。(Python ではありません)
mkdir cdk_demo_project cd cdk_demo_project cdk init --language typescript
Amazon Lambda Python Library 導入
Amazon Lambda Python Library をインストールします。
npm i -D @aws-cdk/aws-lambda-python-alpha
Lambda 関数および CDK コード
Lambda 関数を配置するディレクトリを作成します。
mkdir src & mkdir src/lambda & mkdir src/lambda/hello touch src/lambda/hello/index.py
次の内容の Lambda 関数を作成します。
def handler(event, context): return { 'statusCode': 200, 'body': 'Hello, CDK!' }
PythonFunction
を使って Lambda 関数を CDK コードで実装します。
import { aws_lambda, Stack, StackProps } from 'aws-cdk-lib'; import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; import { Construct } from 'constructs'; export class CdkDemoProjectStack extends Stack { constructor(scope: Construct, id: string, props: StackProps) { super(scope, id, props); new PythonFunction(this, 'helloFunction', { functionName: 'helloFunction', runtime: aws_lambda.Runtime.PYTHON_3_11, entry: 'src/lambda/hello', handler: 'handler', }); } }
デプロイ
さてここから CDK Deploy により Lambda 関数をデプロイしていくのですが、PythonFunction で Lambda 関数をビルドする上で必要な Docker 周りでかなりの試行錯誤があったので、その記録を残しておきます。
CDK Deploy をしようとすると Synth でエラーとなりました。ここで Docker の起動が必要であることに気が付きます。
$ cdk deploy Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? /Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/private/asset-staging.js:2 `).map((line,idx)=>`${idx===0?firstLine:padding}${line}`)};const reason=proc.signal!=null?`signal ${proc.signal}`:`status ${proc.status}`,command=[prog,...args.map(arg=>/[^a-z0-9_-]/i.test(arg)?JSON.stringify(arg):arg)].join(" ");throw new Error([`${prog} exited with ${reason}`,...prependLines("--> STDOUT: ",proc.stdout)??[],...prependLines("--> STDERR: ",proc.stderr)??[],`--> Command: ${command}`].join(` ^ Error: docker exited with status 1 --> Command: docker build -t cdk-ab3de628ee1e2f62b9079f422a090e9043f44ecd2ffb1c3a0c647e07830330bd --platform "linux/amd64" --build-arg "IMAGE=public.ecr.aws/sam/build-python3.11" "/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib" at dockerExec (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/private/asset-staging.js:2:237) at Function.fromBuild (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/bundling.js:1:4124) at new Bundling (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/bundling.ts:100:39) at Function.bundle (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/bundling.ts:61:44) at new PythonFunction (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/function.ts:75:22) at new CdkDemoProjectStack (/Users/wakatsuki.ryuta/projects/cdk_demo_project/lib/cdk_demo_project-stack.ts:9:5) at Object.<anonymous> (/Users/wakatsuki.ryuta/projects/cdk_demo_project/bin/cdk_demo_project-stack.ts:6:1) at Module._compile (node:internal/modules/cjs/loader:1105:14) at Module.m._compile (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/ts-node/src/index.ts:1618:23) at Module._extensions..js (node:internal/modules/cjs/loader:1159:10) Subprocess exited with error 1
Docker を起動させます。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
するとエラーが変わりました。
$ cdk deploy [+] Building 0.3s (3/3) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 1.28kB 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => ERROR [internal] load metadata for public.ecr.aws/sam/build-python3.11:latest 0.3s ------ > [internal] load metadata for public.ecr.aws/sam/build-python3.11:latest: ------ failed to solve with frontend dockerfile.v0: failed to create LLB definition: failed to do request: Head "https://public.ecr.aws/v2/sam/build-python3.11/manifests/latest": Failed to lookup host: public.ecr.aws /Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/private/asset-staging.js:2 `).map((line,idx)=>`${idx===0?firstLine:padding}${line}`)};const reason=proc.signal!=null?`signal ${proc.signal}`:`status ${proc.status}`,command=[prog,...args.map(arg=>/[^a-z0-9_-]/i.test(arg)?JSON.stringify(arg):arg)].join(" ");throw new Error([`${prog} exited with ${reason}`,...prependLines("--> STDOUT: ",proc.stdout)??[],...prependLines("--> STDERR: ",proc.stderr)??[],`--> Command: ${command}`].join(` ^ Error: docker exited with status 1 --> Command: docker build -t cdk-ab3de628ee1e2f62b9079f422a090e9043f44ecd2ffb1c3a0c647e07830330bd --platform "linux/amd64" --build-arg "IMAGE=public.ecr.aws/sam/build-python3.11" "/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib" at dockerExec (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/private/asset-staging.js:2:237) at Function.fromBuild (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/bundling.js:1:4124) at new Bundling (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/bundling.ts:100:39) at Function.bundle (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/bundling.ts:61:44) at new PythonFunction (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/function.ts:75:22) at new CdkDemoProjectStack (/Users/wakatsuki.ryuta/projects/cdk_demo_project/lib/cdk_demo_project-stack.ts:9:5) at Object.<anonymous> (/Users/wakatsuki.ryuta/projects/cdk_demo_project/bin/cdk_demo_project-stack.ts:6:1) at Module._compile (node:internal/modules/cjs/loader:1105:14) at Module.m._compile (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/ts-node/src/index.ts:1618:23) at Module._extensions..js (node:internal/modules/cjs/loader:1159:10) Subprocess exited with error 1
ここで Docker Desktop のバージョンが古いことに気が付いたのでアップグレードします。するとさらにエラーが変わりました。
$ cdk deploy [+] Building 2.2s (4/4) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 37B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => ERROR [internal] load metadata for public.ecr.aws/sam/build-python3.11:latest 2.1s => [auth] aws:: sam/build-python3.11:pull token for public.ecr.aws 0.0s ------ > [internal] load metadata for public.ecr.aws/sam/build-python3.11:latest: ------ failed to solve with frontend dockerfile.v0: failed to create LLB definition: unexpected status code [manifests latest]: 403 Forbidden /Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/private/asset-staging.js:2 `).map((line,idx)=>`${idx===0?firstLine:padding}${line}`)};const reason=proc.signal!=null?`signal ${proc.signal}`:`status ${proc.status}`,command=[prog,...args.map(arg=>/[^a-z0-9_-]/i.test(arg)?JSON.stringify(arg):arg)].join(" ");throw new Error([`${prog} exited with ${reason}`,...prependLines("--> STDOUT: ",proc.stdout)??[],...prependLines("--> STDERR: ",proc.stderr)??[],`--> Command: ${command}`].join(` ^ Error: docker exited with status 1 --> Command: docker build -t cdk-ab3de628ee1e2f62b9079f422a090e9043f44ecd2ffb1c3a0c647e07830330bd --platform "linux/amd64" --build-arg "IMAGE=public.ecr.aws/sam/build-python3.11" "/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib" at dockerExec (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/private/asset-staging.js:2:237) at Function.fromBuild (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/aws-cdk-lib/core/lib/bundling.js:1:4124) at new Bundling (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/bundling.ts:100:39) at Function.bundle (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/bundling.ts:61:44) at new PythonFunction (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/@aws-cdk/aws-lambda-python-alpha/lib/function.ts:75:22) at new CdkDemoProjectStack (/Users/wakatsuki.ryuta/projects/cdk_demo_project/lib/cdk_demo_project-stack.ts:9:5) at Object.<anonymous> (/Users/wakatsuki.ryuta/projects/cdk_demo_project/bin/cdk_demo_project-stack.ts:6:1) at Module._compile (node:internal/modules/cjs/loader:1105:14) at Module.m._compile (/Users/wakatsuki.ryuta/projects/cdk_demo_project/node_modules/ts-node/src/index.ts:1618:23) at Module._extensions..js (node:internal/modules/cjs/loader:1159:10) Subprocess exited with error 1
このエラーは下記が参考になりました。
Docker で ECR にログインをします。
aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws
すると Synth が通り、デプロイまで行えました。
$ cdk deploy [+] Building 135.5s (7/7) FINISHED docker:desktop-linux => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 1.28kB 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for public.ecr.aws/sam/build-python3.11:latest 2.4s => [auth] aws:: sam/build-python3.11:pull token for public.ecr.aws 0.0s => [1/2] FROM public.ecr.aws/sam/build-python3.11@sha256:ad08c7e702a7c2b5c13c60de6180ca4bc97be781a72063a618784596cbda65c6 32.9s => => resolve public.ecr.aws/sam/build-python3.11@sha256:ad08c7e702a7c2b5c13c60de6180ca4bc97be781a72063a618784596cbda65c6 0.0s => => sha256:10f153b3bd74382059f5423e00bb91447ae450a3b566ac8dd34b9072ac49dcf8 2.64kB / 2.64kB 0.0s => => sha256:29a91f87d93088b3ae82542459d827edd30a0bd0d1c2709f2e53982c258cb8b6 87.36kB / 87.36kB 0.2s => => sha256:877e2bfc6ba89a3fc51d12d92799e17d3ec8afefbf7cc868de9e35d078b8faa9 417B / 417B 0.3s => => sha256:ad08c7e702a7c2b5c13c60de6180ca4bc97be781a72063a618784596cbda65c6 772B / 772B 0.0s => => sha256:79a77e7c1be9a2c4f77ead609e8d8b7162377bb6905b2a244c7964d74d8c8762 2.51MB / 2.51MB 1.4s => => sha256:0e9b5ea721abee27f3e34c651182ec82103b32067ef6858f5d432d6b10c10099 7.04kB / 7.04kB 0.0s => => extracting sha256:29a91f87d93088b3ae82542459d827edd30a0bd0d1c2709f2e53982c258cb8b6 0.0s => => sha256:c091401fd8a0aa9d9e34453038b9e08154298a521f3f460a3f0bb2b946e4629d 128.93MB / 128.93MB 6.5s => => extracting sha256:877e2bfc6ba89a3fc51d12d92799e17d3ec8afefbf7cc868de9e35d078b8faa9 0.0s => => sha256:2e3380a3fb2644cc0f902d3001f7d8235102e976431959b3ecdcd7d9b947378f 15.31kB / 15.31kB 0.7s => => sha256:8ceec474a49056b91a037e8617c75feac54d4f014219d0ab05cfb0854f06f171 224.66MB / 224.66MB 13.6s => => extracting sha256:79a77e7c1be9a2c4f77ead609e8d8b7162377bb6905b2a244c7964d74d8c8762 0.0s => => sha256:4b67a36e95c8b60c0373e05feeba88d4875956eac55167b880a8c838cc6f0e14 55.82MB / 55.82MB 11.9s => => sha256:0344a919057170918f8b5889a6c39857e0776683651910a7769bcb0cc283dc73 79.85MB / 79.85MB 15.0s => => extracting sha256:c091401fd8a0aa9d9e34453038b9e08154298a521f3f460a3f0bb2b946e4629d 5.5s => => sha256:fcf86f09cae9b81c5101817003e642f3262b3787a61f1bff6f0ac052032940e7 205.46kB / 205.46kB 12.2s => => sha256:1ac678504b71b45f9cfa05f7a25ae9510e1c2cb432fdcac3a59d58c94c7ad10e 103.92kB / 103.92kB 12.4s => => extracting sha256:2e3380a3fb2644cc0f902d3001f7d8235102e976431959b3ecdcd7d9b947378f 0.0s => => extracting sha256:8ceec474a49056b91a037e8617c75feac54d4f014219d0ab05cfb0854f06f171 10.6s => => extracting sha256:4b67a36e95c8b60c0373e05feeba88d4875956eac55167b880a8c838cc6f0e14 2.3s => => extracting sha256:0344a919057170918f8b5889a6c39857e0776683651910a7769bcb0cc283dc73 4.7s => => extracting sha256:fcf86f09cae9b81c5101817003e642f3262b3787a61f1bff6f0ac052032940e7 0.0s => => extracting sha256:1ac678504b71b45f9cfa05f7a25ae9510e1c2cb432fdcac3a59d58c94c7ad10e 0.0s => [2/2] RUN python -m venv /usr/app/venv && mkdir /tmp/pip-cache && chmod -R 777 /tmp/pip-cache && pip install --upgrade pip && mkdir /tmp/poetry-cache && chmod -R 777 /tmp/po 99.7s => exporting to image 0.5s => => exporting layers 0.5s => => writing image sha256:392b68bf00a6d9cee956f0a5322492f8853b9f35d6f8678fbc47591504c80a38 0.0s => => naming to docker.io/library/cdk-ab3de628ee1e2f62b9079f422a090e9043f44ecd2ffb1c3a0c647e07830330bd 0.0s What's Next? View summary of image vulnerabilities and recommendations → docker scout quickview Bundling asset CdkDemoProjectStack/helloFunction/Code/Stage... WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested sending incremental file list index.py sent 217 bytes received 35 bytes 504.00 bytes/sec total size is 105 speedup is 0.42 ✨ Synthesis time: 139.39s CdkDemoProjectStack: start: Building 8efbea89c33394485407976945c492b1e7e99ef18a4c8c8544f234f2e5490a7d:current_account-current_region CdkDemoProjectStack: success: Built 8efbea89c33394485407976945c492b1e7e99ef18a4c8c8544f234f2e5490a7d:current_account-current_region CdkDemoProjectStack: start: Building 603ed967932afd7da0b3109bd50ddbcea827870adcdc7e1bf0ed9bb0e738d776:current_account-current_region CdkDemoProjectStack: success: Built 603ed967932afd7da0b3109bd50ddbcea827870adcdc7e1bf0ed9bb0e738d776:current_account-current_region CdkDemoProjectStack: start: Publishing 8efbea89c33394485407976945c492b1e7e99ef18a4c8c8544f234f2e5490a7d:current_account-current_region CdkDemoProjectStack: start: Publishing 603ed967932afd7da0b3109bd50ddbcea827870adcdc7e1bf0ed9bb0e738d776:current_account-current_region CdkDemoProjectStack: success: Published 603ed967932afd7da0b3109bd50ddbcea827870adcdc7e1bf0ed9bb0e738d776:current_account-current_region CdkDemoProjectStack: success: Published 8efbea89c33394485407976945c492b1e7e99ef18a4c8c8544f234f2e5490a7d:current_account-current_region CdkDemoProjectStack: deploying... [1/1] CdkDemoProjectStack: creating CloudFormation changeset... ✅ CdkDemoProjectStack ✨ Deployment time: 32.56s Stack ARN: arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/CdkDemoProjectStack/00c2aac0-2abc-11ee-85b9-0a99c3b7c389 ✨ Total time: 171.95s
デプロイした Lambda 関数を実行すると正常に動作していますね。
$ aws lambda invoke \ --function-name helloFunction \ response.json { "StatusCode": 200, "ExecutedVersion": "$LATEST" } $ cat response.json {"statusCode": 200, "body": "Hello, CDK!"}
Lambda 関数にパッケージを追加
PythonFunction
で実装する Lambda 関数にパッケージを追加したい場合は、entry パスに
requirements.txt
Pipfile
poetry.lock
のいずれかを含めれば、よしなにパッケージングをしてデプロイしてくれます。
今回は Poetry を使ってみます。pytz をインストールします。
cd src/lambda/hello poetry init poetry add pytz
すると src/lambda/hello/poetry.lock
が作成されます。また同ディレクトリに pyproject.toml
も作成されますが、これは Git で管理不要なので.gitignore
に追記しておきましょう。
Lambda 関数を pytz を使うように修正して、CDK Deploy します。
from datetime import datetime from pytz import timezone def handler(event, context): tokyo_dt = datetime.now(timezone('Asia/Tokyo')) return { 'statusCode': 200, 'body': tokyo_dt.strftime('%Y-%m-%d %H:%M:%S') }
デプロイした Lambda 関数を実行すると、pytz が使用された結果が返ってきています。良さそうですね。
$ aws lambda invoke \ --function-name helloFunction \ response.json { "StatusCode": 200, "ExecutedVersion": "$LATEST" } $ cat response.json {"statusCode": 200, "body": "2023-07-31 23:13:09"}
ちなみにパッケージングのその他の方法として同ライブラリの PythonLayerVersion
も使用可能です。こちらは Lambda 関数のコードとは別にレイヤーとしてパッケージングされます。
おわりに
AWS CDK(TypeScript)で Python ランタイムの Lambda 関数を実装してみました。
Python や Docker を使うのは久しぶりで四苦八苦しましたが、なんとかデプロイまで行えました。参考になれば幸いです。
参考
以上